#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>
#include <signal.h>
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace ThreadPoolModule;
using namespace lyrics;

//传递一条命令，返回该命令的执行结果
using handler_t = std::function<std::string(std::string)>;

#define BACKLOG 8
const uint16_t gport = 8080;
class TcpServer
{
private:
    //任务类型---和多线程中的任务类型保持一致
    using task_t = std::function<void()>;
    //
    struct ThreadData
    {
        int sockfd;
        TcpServer* server;
    };
public:
    //缺省参数需要写在后面
    TcpServer(handler_t handler,uint16_t port = gport) :_handler(handler), _port(port),_isrunning(false) {}
    void InitServer()
    {
        // Tcp是流式套接所以用的SOCK_STREAM
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket failed : " << strerror(_listensockfd);
            Die(SOCKET_ERR);
        }
        // 创建成功打印sockfd
        LOG(LogLevel::INFO) << "socket success , socketfd is : " << _listensockfd;

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        // 接收任何IP地址的信息
        local.sin_addr.s_addr = INADDR_ANY;
        // 2. bind----绑定本地信息
        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind failed : " << strerror(n);
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind succuss";
        // 3. bind成功之后---Tcp是面相连接的协议---客户端需要连接服务器，服务器就要随时随地等待被连接
        //Tcp需要将socket设置为监听状态，等待客户端随时随地来连接
        int m = listen(_listensockfd,BACKLOG);
        if(m < 0)
        {
            LOG(LogLevel::FATAL) << "listen failed :" << strerror(m);
            Die(LISTEN_ERR);
        }
        //监听成功
        LOG(LogLevel::INFO) << "listen success, sockfd: " << _listensockfd;

        //::signal(SIGCHLD,SIG_IGN); //子进程退出，OS会自动回收子进程，不用wait
    }
    void HandlerRequest(int sockfd) //TCP也是全双工
    {
        char inbuffer[4096];
        //约定用户发送过来的是一个完整的额命令string
        //长任务处理，不能在这里阻塞，所以使用线程池----线程池一般用于处理短任务
        while(true)
        {
            //读取请求---accept返回的是文件描述符---面相字节流：管道，文件，TCP传输
            //UDP是面向数据报----因为这里是文件描述符，所以可以进行重定向等文件操作
            ssize_t n = ::recv(sockfd,inbuffer,sizeof(inbuffer) - 1,0);//这里read读取不一定会把inbuffer读取完（读取不完善）
            if(n > 0)
            {
                inbuffer[n] = 0;
                LOG(LogLevel::DEBUG) << inbuffer;
                // std::string echo_str = "server echo# ";
                // echo_str += inbuffer;

                //解析命令，将结果给客户端
                std::string cmd_result = _handler(inbuffer);
                ::send(sockfd,cmd_result.c_str(),cmd_result.size(),0);//写入也不完善，当发送helloworld的时候可能先传递过来的是hello不会将整个字符串完整的传递过来
            }
            else if(n == 0)
            {
                //read如果读取返回值是0，表示client退出
                LOG(LogLevel::INFO) << "client quit: " << sockfd;
                break; 
            }
            else 
            {
                //读取失败
                break;
            }
        }
        //关闭文件描述符----文件描述符属于有限资源
        ::close(sockfd); //fd泄露问题
    }
    //因为第一个参数是this指针，所以需要加上static
    static void *ThreadEntry(void* arg)
    {
        //进行线程分离
        pthread_detach(pthread_self());
        ThreadData* data = (ThreadData*)arg;
        //这里直接调用data的成员方法，而不是this指针
        data->server->HandlerRequest(data->sockfd);
        return nullptr;
    }
    void Start()
    {
        _isrunning = true;
        
        while(_isrunning)
        {
            char inbuffer[4096];
            //Tcp服务器不能直接读消息
            //1. 获取新连接----sockfd用于提供网络服务
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            LOG(LogLevel::DEBUG) << "accept ing...."; 
            //listen套接字只用来接收新连接，sockfd用来进行实现网络服务
            int sockfd = ::accept(_listensockfd,CONV(&peer),&len);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept failed : " << strerror(sockfd);
                //服务器accept失败继续接收服务
                continue;
            }
            //获取连接成功
            LOG(LogLevel::INFO) << "accept success: " << sockfd;
            int n = recv(sockfd,inbuffer,sizeof(inbuffer) - 1,0);
            inbuffer[n] = 0;
            std::string echostr = _handler(inbuffer);
            
            n = ::send(sockfd,echostr.c_str(),echostr.size(),0);
            //version-0----测试---无法同时处理多个客户端
            //HandlerRequest(sockfd);

            //version-1：多进程版本
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     //child
            //     //问题1：父进程打开的文件描述符，子进程能看到吗？-----可以看到，子进程可以继承父进程文件描述符表
            //     //两张文件描述符表，父子进程各一张
            //     //子进程需要关闭不需要的文件描述符
            //     ::close(_listensockfd); //继承的父进程的父进程的listen
            //     if(fork() > 0) exit(0); //子进程退出

            //     //往后走的是孙子进程---孤儿进层---->会被操作系统1号进程领养回收
            //     HandlerRequest(sockfd); 
            //     exit(0);
            // }
            // //关闭父进程不关心的文件描述符
            // ::close(sockfd);
            // //parent
            // int rid = waitpid(id,nullptr,0);
            // if(rid < 0)
            // {
            //     LOG(LogLevel::WARNING) << "waitpid failed";
            // }


            //version-2：多线程版本
            // 主线程和新线程共享一张文件描述符表

            // pthread_t tid;
            // //这个线程被创建之后，是不能关闭文件描述符的，因为公用一张文件描述符表
            // ThreadData *data = new ThreadData;//这里为什么要new一段空间？因为有可能当新线程没有跑的时候，主线程就已经释放了这段空间，导致程序崩溃
            // data->sockfd = sockfd;
            // data->server = this;
            // int ret = pthread_create(&tid,nullptr,ThreadEntry,(void*)data);//主线程和新线程是如何看待历史文件描述符表的


            // //等待线程结束
            // pthread_join(tid,nullptr);

            //version-3：线程池版本---比较适合处理短任务，或者是用户量少的情况
            //task_t f = std::bind(&TcpServer::HandlerRequest,this,sockfd); //构建任务
            //将任务入到单例当中
            // ThreadPool<task_t>::getInstance()->Equeue([this,sockfd](){
            //     this->HandlerRequest(sockfd);
            // });
        }
        _isrunning = false;
    }
    ~TcpServer(){}
private:
    int _listensockfd; //专门获取新链接的
    uint16_t _port;
    bool _isrunning;
    handler_t _handler;
};